package indi.zhifa.core.multidatasource.core.factorbean;

import com.baomidou.mybatisplus.autoconfigure.*;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import indi.zhifa.core.mpenhance.config.BaseMybatisPlusConfig;
import indi.zhifa.core.multidatasource.core.MultiDataSourceTransactionFactory;
import indi.zhifa.core.multidatasource.enums.EDataSourceType;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.*;
import java.util.function.Consumer;

public class SqlSessionFactorBean implements FactoryBean<SqlSessionFactory> {

    private final DataSource mDataSource;

    private final ObjectProvider<MybatisPlusProperties> mPropertiesProvider;
    private final ResourceLoader mResourceLoader;
    private final ApplicationContext mApplicationContext;
    private final EDataSourceType mDataSourceType;

    /* mp用到的一些变量*/

    private final ObjectProvider<Interceptor> mInterceptorsProvider;
    private final ObjectProvider<TypeHandler> mTypeHandlersProvider;
    private final ObjectProvider<LanguageDriver> mLanguageDriversProvider;
    private final ObjectProvider<DatabaseIdProvider> mDatabaseIdProviderProvider;
    private final ObjectProvider<List<ConfigurationCustomizer>> mConfigurationCustomizersProvider;
    private final ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> mSqlSessionFactoryBeanCustomizersProvider;
    private final ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mMybatisPlusPropertiesCustomizersProvider;


    private Interceptor[] mInterceptors;
    private TypeHandler[] mTypeHandlers;
    private LanguageDriver[] mLanguageDrivers;
    private DatabaseIdProvider mDatabaseIdProvider;
    private List<ConfigurationCustomizer> mConfigurationCustomizers;
    private List<SqlSessionFactoryBeanCustomizer> mSqlSessionFactoryBeanCustomizers;
    private List<MybatisPlusPropertiesCustomizer> mMybatisPlusPropertiesCustomizers;
    private MybatisPlusProperties mMybatisPlusProperties;



    public SqlSessionFactorBean(EDataSourceType pDataSourceType,
                                DataSource pDataSource,
                                ObjectProvider<MybatisPlusProperties> propertiesProvider,
                                ResourceLoader resourceLoader,
                                ApplicationContext applicationContext,

                                ObjectProvider<Interceptor> interceptorsProvider,
                                ObjectProvider<TypeHandler> typeHandlersProvider,
                                ObjectProvider<LanguageDriver> languageDriversProvider,
                                ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
                                ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers,
                                ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider) {
        mDataSourceType = pDataSourceType;
        mDataSource = pDataSource;
        this.mPropertiesProvider = propertiesProvider;
        this.mInterceptorsProvider = interceptorsProvider;
        this.mTypeHandlersProvider = typeHandlersProvider;
        this.mLanguageDriversProvider = languageDriversProvider;
        this.mResourceLoader = resourceLoader;
        this.mDatabaseIdProviderProvider = databaseIdProvider;
        this.mConfigurationCustomizersProvider = configurationCustomizersProvider;
        this.mSqlSessionFactoryBeanCustomizersProvider = sqlSessionFactoryBeanCustomizers;
        this.mMybatisPlusPropertiesCustomizersProvider = mybatisPlusPropertiesCustomizerProvider;
        this.mApplicationContext = applicationContext;
    }

    @Override
    public SqlSessionFactory getObject() throws Exception {
        getMpBeans();
        switch (mDataSourceType){
            case MULTI -> {
                MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
                MultiDataSourceTransactionFactory transactionFactory = new MultiDataSourceTransactionFactory();
                sqlSessionFactory(bean,mDataSource);
                bean.setTransactionFactory(transactionFactory);
                return bean.getObject();
            }
            case SINGLE -> {
                MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
                sqlSessionFactory(bean,mDataSource);
                return bean.getObject();
            }
        }
        return null;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    protected void getMpBeans(){
        mInterceptors = mInterceptorsProvider.stream().toArray(Interceptor[]::new);
        mTypeHandlers = mTypeHandlersProvider.stream().toArray(TypeHandler[]::new);
        mLanguageDrivers = mLanguageDriversProvider.stream().toArray(LanguageDriver[]::new);
        mDatabaseIdProvider = mDatabaseIdProviderProvider.getIfAvailable();
        mConfigurationCustomizers = mConfigurationCustomizersProvider.getIfAvailable();
        mSqlSessionFactoryBeanCustomizers = mSqlSessionFactoryBeanCustomizersProvider.getIfAvailable();
        mMybatisPlusPropertiesCustomizers = mMybatisPlusPropertiesCustomizersProvider.getIfAvailable();
        mMybatisPlusProperties = mPropertiesProvider.getIfAvailable();
    }


    public MybatisSqlSessionFactoryBean sqlSessionFactory(MybatisSqlSessionFactoryBean pMybatisSqlSessionFactoryBean ,DataSource dataSource) throws Exception {

        MybatisSqlSessionFactoryBean factory = pMybatisSqlSessionFactoryBean;
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.mMybatisPlusProperties.getConfigLocation())) {
            factory.setConfigLocation(this.mResourceLoader.getResource(this.mMybatisPlusProperties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.mMybatisPlusProperties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.mMybatisPlusProperties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.mInterceptors)) {
            factory.setPlugins(this.mInterceptors);
        }
        if (this.mDatabaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.mDatabaseIdProvider);
        }
        if (StringUtils.hasLength(this.mMybatisPlusProperties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.mMybatisPlusProperties.getTypeAliasesPackage());
        }
        if (this.mMybatisPlusProperties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.mMybatisPlusProperties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.mMybatisPlusProperties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.mMybatisPlusProperties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.mTypeHandlers)) {
            factory.setTypeHandlers(this.mTypeHandlers);
        }
        if (!ObjectUtils.isEmpty(this.mMybatisPlusProperties.resolveMapperLocations())) {
            factory.setMapperLocations(this.mMybatisPlusProperties.resolveMapperLocations());
        }

        Class<? extends LanguageDriver> defaultLanguageDriver = this.mMybatisPlusProperties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.mLanguageDrivers)) {
            factory.setScriptingLanguageDrivers(this.mLanguageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

        applySqlSessionFactoryBeanCustomizers(factory);

        GlobalConfig globalConfig = this.mMybatisPlusProperties.getGlobalConfig();
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        this.getBeanThen(AnnotationHandler.class, globalConfig::setAnnotationHandler);
        this.getBeanThen(PostInitTableInfoHandler.class, globalConfig::setPostInitTableInfoHandler);
        this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        factory.setGlobalConfig(globalConfig);
        return factory;
    }


    private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
        if (this.mApplicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            consumer.accept(this.mApplicationContext.getBean(clazz));
        }
    }

    private <T> void getBeansThen(Class<T> clazz, Consumer<List<T>> consumer) {
        if (this.mApplicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            final Map<String, T> beansOfType = this.mApplicationContext.getBeansOfType(clazz);
            List<T> clazzList = new ArrayList<>();
            beansOfType.forEach((k, v) -> clazzList.add(v));
            consumer.accept(clazzList);
        }
    }

    private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {
        MybatisPlusProperties.CoreConfiguration coreConfiguration = this.mMybatisPlusProperties.getConfiguration();
        MybatisConfiguration configuration = null;
        if (coreConfiguration != null || !StringUtils.hasText(this.mMybatisPlusProperties.getConfigLocation())) {
            configuration = new MybatisConfiguration();
        }
        if (configuration != null && coreConfiguration != null) {
            coreConfiguration.applyTo(configuration);
        }
        if (configuration != null && !CollectionUtils.isEmpty(this.mConfigurationCustomizers)) {
            for (ConfigurationCustomizer customizer : this.mConfigurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        factory.setConfiguration(configuration);
    }

    private void applySqlSessionFactoryBeanCustomizers(MybatisSqlSessionFactoryBean factory) {
        if (!CollectionUtils.isEmpty(this.mSqlSessionFactoryBeanCustomizers)) {
            for (SqlSessionFactoryBeanCustomizer customizer : this.mSqlSessionFactoryBeanCustomizers) {
                customizer.customize(factory);
            }
        }
    }



}
